<template>
  <div>
    <div class="tool-box">
      <div class="logo-box">
        <h3>TPSHION</h3>
      </div>
      <div class="btn-box">
        <div class="opt-box">
          <el-button>{{ taskWork.name }}</el-button>
          <el-button type="primary" :icon="Share" @click="openBranch">{{ getRunBranch(taskWork.nodeDataList) }}</el-button>
          <el-button type="success" :icon="VideoPlay" :loading="taskStatus" @click="handleTask">立即构建</el-button>
          <el-button type="danger" :icon="VideoPause" @click="handleTask" v-show="taskStatus">停止</el-button>
          <el-button type="info" :icon="Histogram" @click="handleRunLog">构建历史</el-button>
          <el-button type="warning" :icon="CircleClose" @click="backHome">退出</el-button>
        </div>
      </div>
    </div>
    <div class="flow-box">
      <el-row>
        <el-col :span="24">
          <div class="flow-list">
            <div ref="container" class="container-wrap"></div>
            <TeleportContainer />
          </div>
        </el-col>
        <el-col :span="24">
          <div class="log-box">
            <div class="log-wrap" id="log-container">
              <p v-for="(item,index) in msgList" :key="index" :style="{color: getMsgColor(item.type)}"><span style="color: #A8ABB2;">{{ item.time }}</span> {{ item.msg }}</p>
            </div>
          </div>
        </el-col>
      </el-row>
    </div>

    <el-dialog
      v-model="dialogVisible"
      title="切换分支"
      width="30%"
      :before-close="handleClose"
    >
      <div v-loading="loading">
        <el-row>
          <el-col :span="24">
            <span>当前分支：<span style="color: #409EFF;">{{ getRunBranch(taskWork.nodeDataList) }}</span></span>
          </el-col>
          <el-col :span="24">
            <el-select style="margin-top: 10px;" v-model="selectBranch" placeholder="选择分支" size="small">
              <el-option
                v-for="(item,index) in branchList"
                :key="index"
                :label="item"
                :value="item"
              />
            </el-select>
          </el-col>
        </el-row>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="handleClose">取消</el-button>
          <el-button type="primary" @click="handleBranch">确定</el-button>
        </span>
      </template>
    </el-dialog>

    <el-drawer v-model="drawer" :direction="direction" size="850" :title="drawerTitle" :before-close="handleCloseDrawer">
      <el-table :data="hisList" height="calc(100vh - 120px)" v-loading="drawerLoading">
        <el-table-column width="90" label="构建序号" fixed>
          <template #default="scope">
            <el-tooltip effect="light" content="查看日志" placement="top-start">
              <span style="color: #409EFF; cursor: pointer;" @click="openHistoryLog(scope.row)">#{{ scope.row.batchId }}</span>
            </el-tooltip>
          </template>
        </el-table-column>
        <el-table-column width="70" label="状态">
          <template #default="scope">
            <el-tag v-if="scope.row.status === 'success'" type="success">成功</el-tag>
            <el-tag v-if="scope.row.status === 'error'" type="danger">失败</el-tag>
            <el-tag v-if="scope.row.status === 'stop'" type="warning">中断</el-tag>
          </template>
        </el-table-column>
        <el-table-column width="100" label="分支">
          <template #default="scope">
            <el-link style="color: #409EFF;" :icon="Share">{{ scope.row.branch }}</el-link>
          </template>
        </el-table-column>
        <el-table-column width="80" property="totalTime" label="耗时" />
        <el-table-column width="100" label="git版本">
          <template #default="scope">
            <span>{{ scope.row.commitVersion ? scope.row.commitVersion.substring(0,8) : '' }}</span>
          </template>
        </el-table-column>
        <el-table-column width="108" property="startTime" label="开始时间" />
        <el-table-column width="108" property="endTime" label="结束时间" />
        <el-table-column property="commitMsg" label="提交信息" />
      </el-table>
    </el-drawer>

    <el-dialog
      v-model="logShow"
      :title="logTitle"
      fullscreen
      :before-close="handleLogClose"
    >
      <div v-loading="logLoading" class="his-log">
        <p v-for="(item,index) in logList" :key="index">{{ item }}</p>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="handleLogClose">关闭</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref, nextTick, onBeforeUnmount,  } from 'vue'
import TaskNode from '../components/TaskNode.vue'
import { useRouter, useRoute } from 'vue-router'
import { Share, VideoPause, VideoPlay, CircleClose, Histogram } from '@element-plus/icons-vue'
import { Graph } from '@antv/x6'
import { saveFlowData, getBranchList } from '../api/TaskApi'
import { register, getTeleport} from '@antv/x6-vue-shape'
import { ElLoading, ElMessage } from 'element-plus'
import { TaskWork, MsgData, NodeStatusData, FlowStatusData, FlowHistory } from "../model/TaskModel";
import { GitData, NodeData } from '../model/NodeModel'
import {close } from '../api/SSEApi'
import { startTask, stopTask, getTaskStatus, getRunStatus } from '../api/NodeApi'
import { historyList, historyLog } from '../api/FlowApi'

const router = useRouter()
const route = useRoute()
const TeleportContainer = getTeleport()
const taskId = route.query.taskId as string
const taskWork = ref<TaskWork>({
  name: '',
  taskId: '',
  nodeDataList:[],
  flowData: ''
})
let graph:Graph
const container = ref<HTMLElement>({} as HTMLElement)
const branchList = ref<string[]>([])
const dialogVisible = ref<boolean>(false)
const selectBranch = ref<string>('')
const loading = ref<boolean>(true)
const sseClient = ref<EventSource>()
const clientId = ref<string>('')
const msgList = ref<MsgData[]>([])
const taskStatus = ref<boolean>(false)
const hisList = ref<FlowHistory[]>([])
const drawer = ref(false)
const direction = ref('rtl')
const drawerTitle = ref<string>()
const drawerLoading = ref<boolean>(false)
const logList = ref<string[]>([])
const logShow = ref<boolean>(false)
const logLoading = ref<boolean>(false)
const logTitle = ref<string>('构建日志')

register({
  shape: 'task-node',
  width: 110,
  height: 40,
  component: TaskNode,
  ports: {
    groups: {
      left: {
        position: 'left',
        attrs: {
          circle: {
            magnet: true,
            stroke: '#8f8f8f',
            r: 5,
          },
        },
      },
      right: {
        position: 'right',
        attrs: {
          circle: {
            magnet: true,
            stroke: '#8f8f8f',
            r: 5,
          },
        },
      },
    },
  },
})

onMounted(() => {
  initWork()
  initSSEClient()
})

/**
 * 初始化工作空间
 */
 const initWork = async () => {
  await initTaskWork(taskId)
  await initGraph()
}

/**
 * 初始化任务数据
 * @param taskId 任务id
 */
 const initTaskWork = async (taskId:string) => {
  if(!taskId){
    return
  }
  const loading = getLoading('流程数据加载中...')
  const res = await getTaskStatus(taskId)
  loading.close()
  if(res.data){
    taskWork.value = res.data.taskWork
    taskStatus.value = res.data.running
  }
}

const getLoading = (msg:string):any  =>{
  return ElLoading.service({
    lock: true,
    text: msg,
    background: 'rgba(0, 0, 0, 0.7)',
  })
}

const buildGraph = ():Graph => {
  return new Graph({
    container: container.value,
    autoResize: true,
    background: {
      color: '#F2F7FA',
    },
    interacting: false,
  })
}

/**
 * 初始化画板
 */
 const initGraph = async () => {
  graph = buildGraph()

  if(taskWork.value.flowData){
    graph.fromJSON(JSON.parse(taskWork.value.flowData))
    graph.getNodes().forEach(i => {
      taskWork.value.nodeDataList.forEach(t => {
        if(i.id == t.id){
          i.data = t
        }
      })
    })
  }
  
  graph.zoomToFit({ padding: 10, maxScale: 1 })
  graph.centerContent()
}

const backHome = () => {
  router.push({path: '/'})
}

const scrollToBottom = () =>{
  const box = document.getElementById('log-container');
  if(!box){
    return
  }
  nextTick(() => {
    box.scrollTop = box.scrollHeight
  })
}

const openBranch = async () => {
  selectBranch.value = getRunBranch(taskWork.value.nodeDataList)
  dialogVisible.value = true
  // 查询分支列表
  loading.value = true
  const rs = await getBranchList(taskId)
  loading.value = false
  if(rs.data){
    branchList.value = rs.data
  }else{
    branchList.value = []
  }
}

const handleClose = ()=>{
  dialogVisible.value = false
}

/**
 * 获取运行分支
 */
 const getRunBranch = (nodeDataList: NodeData[] | undefined):string => {
  if(!nodeDataList || nodeDataList.length <= 0){
    return 'master'
  }

  const gitDataList = nodeDataList.filter(i => i.type == 'git')
  if(!gitDataList || gitDataList.length <= 0){
    return 'master'
  }

  const data = gitDataList[0].data as GitData
  return data.branch ? data.branch : data.defaultBranch
}

const handleBranch = async ()=>{
  const gitList = taskWork.value.nodeDataList.filter(i => i.type == 'git')
  const node = graph.getNodes().find(i => i.id == gitList[0].id)
  if(!node){
    ElMessage.error('数据错误')
    return
  }
  const gitData = node.data.data as GitData
  gitData.branch = selectBranch.value
  graph.getNodes().forEach(i => {
    i.data.status = 'init'
  })
  taskWork.value.nodeDataList.forEach(i => {
    i.status = 'init'
  })

  const data = graph.toJSON({ diff: true })
  taskWork.value.flowData = JSON.stringify(data)
  gitList[0].data.branch = selectBranch.value

  const loading = getLoading('正在保存数据...')
  await saveFlowData(taskWork.value)
  loading.close()
  ElMessage.success('保存成功')
  dialogVisible.value = false
}

const initSSEClient = () => {
  if(!window.EventSource){
    ElMessage.error('浏览器不支持SSE')
    return
  }

  sseClient.value = new EventSource(import.meta.env.VITE_BASE_SEE + '/sse/conn?taskId=' + taskId + '&id=' + clientId.value)

  sseClient.value.onopen = () => {
    
  }

  sseClient.value.onmessage = (e) => {
    clientId.value = e.lastEventId
    const msgData = JSON.parse(e.data) as MsgData
    if('node' == msgData.type){
      nodeStatusHandle(msgData)
      return
    }

    if('flow' == msgData.type){
      flowStatusHandle(msgData)
      return
    }

    if('error' == msgData.type){
      errorLogHandle()
    }
    setTimeout(() => {
      if(msgList.value.length > 1000){
        msgList.value = msgList.value.splice(300, msgList.value.length)
      }
      msgList.value.push(msgData)
      scrollToBottom()
    }, 500)
  }

  sseClient.value.onerror = () => {
    if(sseClient.value){
      sseClient.value.close()
    }
  }
}

onBeforeUnmount(() => {
  closeSSEClient()
})

window.onbeforeunload = function () {
  closeSSEClient()
}

const closeSSEClient = () => {
  if(clientId.value){
    close(taskId, clientId.value)
  }
  if(sseClient.value){
    sseClient.value.close()
  }
}

const getMsgColor = (type:string):string => {
  if('info' == type){
    return '#ffffff'
  }
  if('error' == type){
    return '#F56C6C'
  }
  if('success' == type){
    return '#67C23A'
  }

  return '#ffffff'
}

const handleTask = async () => {
  if(taskStatus.value){
    await stopTask(taskId)
    taskStatus.value = false
    // 二次检查运行状态
    await getRunStatus(taskId)
  }else{
    await initWork()
    await startTask(taskId)
    taskStatus.value = true
    msgList.value = []
    if(!sseClient.value || sseClient.value.readyState != 1){
      initSSEClient()
    }
  }
}

const nodeStatusHandle = (msgData:MsgData) => {
  const nodeStatusData = msgData.data as NodeStatusData
  graph.getNodes().forEach(i => {
    if(i.id == nodeStatusData.id){
      i.setData({
        status: nodeStatusData.status
      })
    }
  })
}

const flowStatusHandle = (msgData:MsgData) => {
  const flowStatusData = msgData.data as FlowStatusData
  taskStatus.value = flowStatusData.status
  if(taskStatus.value){
    msgList.value = []
  }
}

const errorLogHandle = () => {
  taskStatus.value = false
}

const handleRunLog = async () => {
  drawerTitle.value = taskWork.value.name
  drawerLoading.value = true
  const rs = await historyList(taskWork.value.taskId)
  if(rs.data){
    hisList.value = rs.data
  }else{
    hisList.value = []
  }
  drawerLoading.value = false
  drawer.value = true
}

const handleCloseDrawer = () => {
  drawer.value = false
  hisList.value = []
}

const openHistoryLog = async (flowHistory:FlowHistory) => {
  logShow.value = true
  logLoading.value = true
  logTitle.value = taskWork.value.name + " (#" + flowHistory.batchId + ")"
  const rs = await historyLog(flowHistory.taskId, flowHistory.batchId)
  if(rs.data){
    logList.value = rs.data
  }else{
    logList.value = []
  }
  logLoading.value = false
}

const handleLogClose = () => {
  logShow.value = false
  logList.value = []
}

</script>

<style lang="less" scoped>
.tool-box{
  height: 60px;
  background-color: #7a7fd3;
  display: flex;
  align-items: center;
  justify-content: end;

  .logo-box{
    width: 100px;
    height: 60px;
    color: #ffffff;
    position: absolute;
    left: 15px;
  }

  .btn-box{
    height: 60px;
    padding-left: 10px;
    padding-right: 10px;
    display: flex;
    align-items: center;

    .title-box{
      width: 250px;
      height: 60px;
      color: #ffffff;
      display: flex;
      align-items: center;
    }

    .opt-box{
      height: 60px;
      display: flex;
      align-items: center;
    }
  }
}

.flow-box{
  height: calc(100vh - 60px);
  .flow-list{
    height: 200px;
    margin-top: 5px;
    padding-left: 10px;
    padding-right: 10px;
    display: flex;

    .container-wrap{
      flex: 1;
      border-radius: 5px;
    }
  }
  
  .log-box{
    height: calc(100vh - 300px);
    padding: 10px;

    .log-wrap{
      height: 100%;
      background-color: #000000;
      padding: 10px;
      border-radius: 5px;
      color: #FFFFFF;
      overflow-y: scroll;
      overflow-wrap: break-word;

      p{
        margin: 0;
      }
    }
  }
}

.his-log{
  height: calc(100vh - 200px); 
  background-color: #000000; 
  color: #FFFFFF; 
  padding: 10px; 
  border-radius: 5px;
  overflow: scroll;
}
</style>